package org.df.ulab.bot.engine;

import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import org.df.ulab.bot.api.communication.CommunicationService;
import org.df.ulab.bot.api.communication.CommunicationService.DefaultMsgs;
import org.df.ulab.bot.api.core.Engine;
import org.df.ulab.bot.api.message.Message;
import org.df.ulab.bot.api.message.MessageHandler;
import org.df.ulab.bot.util.core.EngineSession;
import org.df.ulab.bot.util.core.StorageSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Daniel Filgueiras
 * 04/05/2011
 */
public class CoreEngine implements Runnable, Engine {

	private final Logger logger = LoggerFactory.getLogger(CoreEngine.class);
	private CommunicationService comService;
	private List<MessageHandler> messageHandler = new ArrayList<MessageHandler>();
	private Queue<Class<?>> pedingHandler = new LinkedBlockingQueue<Class<?>>();
	private Queue<Class<?>> haltHandlers = new LinkedBlockingQueue<Class<?>>();
	private Set<String> handlersClasses = new HashSet<String>();
	private Thread thread;
	private long messageCount;
	private String id;
	
	CoreEngine(String id) throws Exception {
		this.id = id;
		this.messageCount = 0;
	}
	
	@Override
	public String getId() {
		return id;
	}
	@Override
	public long getMessageCount() {
		return messageCount;
	}
	
	/* (non-Javadoc)
	 * @see lab.bot.engine.Engine#addMessageHandler(lab.bot.api.MessageHandler)
	 */
	@Override
	public void addMessageHandler(Class<MessageHandler> handler) {
		pedingHandler.add(handler);
	}
	
	@Override
	public void haltMessageHandler(Class<MessageHandler> handler) {
		haltHandlers.add(handler);
	}
	
	private void startPendingHandlers() {
		MessageHandler newHandler = null;
		
		Class<?> newHandlerClass = pedingHandler.poll();
		while (newHandlerClass != null) {
			try {
				if (handlersClasses.contains(newHandlerClass.getName())){
					logger.warn("{} is already started", newHandlerClass);
				} else {
					newHandler = (MessageHandler) newHandlerClass.newInstance();
					
					logger.info("Starting Handler {}", newHandler.getClass().getSimpleName());
					newHandler.init(comService);	//FIXME: Handle false initialization
					messageHandler.add(newHandler);
					handlersClasses.add(newHandlerClass.getName());
				}
			} catch (Exception e) {
				logger.error("Unable to start handler {}", newHandlerClass, e);
			}
			
			newHandlerClass = pedingHandler.poll();
		}
		
		Class<?> remHandlerClass = haltHandlers.poll();
		while (remHandlerClass != null) {
			if (handlersClasses.contains(remHandlerClass.getName())){
				handlersClasses.remove(remHandlerClass.getName());
			}
			
			Iterator<MessageHandler> i = messageHandler.iterator();
			while (i.hasNext()) {
				if (i.next().getClass().equals(remHandlerClass)){
					i.remove();
				}
			}
			remHandlerClass = haltHandlers.poll();
		}
	}
	
	private void initHandlers() {
		pedingHandler.offer(CoreHandler.class);
		
		CoreSettings settings = StorageSystem.getInstance().load(CoreSettings.class);
		
		Set<Class<?>> handlerAutoStart =  settings.getHandlerAutoStart();
		
		for (Class<?> auto : handlerAutoStart) {
			if (handlersClasses.contains(auto.getName())) {
				logger.warn("{} is already started", auto);
			} else {
				try {
					pedingHandler.offer(auto);
				} catch (Exception e) {
					logger.error("Unable to find handler {}", auto, e);
				}
			}
		}
	}
	
	@Override
	public void run() {

		StorageSystem.init(id);
		StorageSystem system = StorageSystem.getInstance();
		CoreSettings settings = system.load(CoreSettings.class);
		Class<? extends CommunicationService> serviceClass = settings.getService();
		try {
			comService = serviceClass.newInstance();
		} catch (Exception e) {
			logger.error("Unable create service {}", serviceClass, e);
			return;
		}
		
		thread = new Thread(comService,  Thread.currentThread().getName() + ".Communication");
		thread.start();
		
		EngineSession.setCommunicationService(comService);
		EngineSession.setEngine(this);
		
		initHandlers();
		
		BlockingQueue<Message> income = comService.getIncome();
		Message message = null;
		while (CoreMain.isAlive() && EngineSession.isAlive() && !thread.getState().equals(State.TERMINATED)) {
			try {
				
				startPendingHandlers();
				
				message = income.poll(60,TimeUnit.SECONDS);
				if (message == null)
					continue;
				
				messageCount++;
				logger.info("Msg N{}: ({})",messageCount, message);

				boolean unknown = true;
				for (MessageHandler handler : messageHandler) {
					if (handler.handle(message)){
						unknown = false;
					}
				}
				
				if (unknown) {
					
					if (Pattern.matches("^//\\*\\*\\*/$", message.toText())) {
						logger.info("Abort!!!");
						CoreMain.coreShutdown();
					} else {
						comService.reply(message, DefaultMsgs.Unknown_Command);
					}
				}
				
				system.persist();
				
			} catch (InterruptedException e) {
				break;
			} catch (Exception e) {
				logger.error("Implementation error",e);
			}
		}
		
		comService.stopService();
		thread.interrupt();
		
		for (MessageHandler handler : messageHandler) {
			handler.close();
		}
	}
}
